package com.apes.fn.server.servicePlan.service.planNodeService;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.apes.crm.business.model.InsuranceCompanyDetail;
import com.apes.fn.business.fixedLoss.modle.LossQuotation;
import com.apes.fn.business.fixedLoss.repository.LossQuotationRepository;
import com.apes.fn.business.fixedLoss.service.FixedLossToForeign;
import com.apes.fn.scm.contract.FnContract;
import com.apes.fn.scm.contract.FnContractRepository;
import com.apes.fn.scm.insuranceRules.InsuranceRule;
import com.apes.fn.scm.insuranceRules.InsuranceRuleService;
import com.apes.fn.server.servicePlan.model.QualityDetail;
import com.apes.fn.server.servicePlan.model.ServicePlan;
import com.apes.fn.server.servicePlan.model.ServicePlanItem;
import com.apes.fn.server.servicePlan.repository.QualityDetailRepository;
import com.apes.fn.server.servicePlan.repository.ServicePlanRepository;
import com.apes.fn.server.servicePlan.service.ServicePlanToInvoke;
import com.apes.fn.server.servicePlan.service.ServicePlanceService;
import com.apes.framework.api.DomainService;
import com.apes.framework.api.SpringManager;
import com.apes.framework.jpa.spec.JpaDsl;
import com.apes.framework.plugin.esb.api.SimpleRequest;
import com.apes.framework.plugin.outbox.OutboxEvent;
import com.apes.framework.plugin.outbox.OutboxEventRepository;
import com.apes.framework.plugin.outbox.OutboxEventService;
import com.apes.framework.plugin.outbox.OutboxPayloadWrapper;
import com.apes.framework.plugin.outbox.annotation.OutboxSubscriber;
import com.apes.framework.util.*;
import com.apes.scm.account.ledger.service.LedgerService;
import com.apes.scm.invoice.model.Invoice;
import com.apes.scm.invoice.repository.InvoiceRepository;
import com.apes.scm.masterdata.party.model.PartyRole;
import com.apes.scm.masterdata.party.repository.PartyRoleRepository;
import com.mongodb.BasicDBObject;
import com.mongodb.Block;
import com.mongodb.client.FindIterable;
import org.bson.Document;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.util.StopWatch;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @program: apes-server
 * @description:
 * @author: czs
 * @create: 2023-04-21 13:59
 **/
@Service("qualityDetailService")
public class QualityDetailService extends DomainService {
    @Autowired
    private QualityDetailRepository qualityDetailRepository;
    @Autowired
    private ServicePlanceService servicePlanceService;
    @Autowired
    private ServicePlanRepository servicePlanRepository;
    @Autowired
    private TransactionTemplate transactionTemplate;
    @Autowired
    private OutboxEventRepository outboxEventRepository;
    @Autowired
    private MongoTemplate mongoTemplate;
    public static final String CONST_ERROR_COLLECTION_NAME = "qualityDetailError";
    public static final String CONST_RUNTIME_COLLECTION_NAME = "qualityDetailRuntimeData";


    public String generateQualityDetail(List<String> ids, JSONObject param){
        StringBuffer sf = new StringBuffer();
        JSONArray pic = param.get("pic")!=null?param.getJSONArray("pic"):new JSONArray();
        JSONObject operator = param.getJSONObject("operator");
        if (operator==null)throw new RuntimeException("操作员不能为空");
        for (String id : ids) {
            sf.append(id);
        }
        String strIds = sf.toString();
        String encrypt = MD5Util.encrypt(strIds);
        for (String id : ids) {
            //校验是否可质检
            checkNeedQualityTest(id);

            QualityDetail qualityDetail = new QualityDetail();
//            qualityDetail.setId(id);
            qualityDetail.setQualityOrderId(encrypt);
            qualityDetail.setPlanId(id);
            qualityDetail.setOperatorId(operator.getString("id"));
            qualityDetail.setQualityTestPic(pic.toJSONString());
            qualityDetail.setQualityFlag(param.getBoolean("qualityFlag"));
            qualityDetailRepository.saveAndFlush(qualityDetail);
            sendQualityTest(qualityDetail);
        }
        return encrypt;
    }

    private void checkNeedQualityTest(String id) {
        ServicePlan servicePlan = servicePlanRepository.findOne(id);
        if (servicePlan.getQualityTestDate()!=null)throw new RuntimeException("单据:"+id+" 已质检");
        if (servicePlan.getTerminationDate()!=null)throw new RuntimeException("单据:"+id+" 已终止");
        List<ServicePlanItem> servicePlanItems = servicePlan.returnNeedQualityTestItems();
        if (servicePlanItems.isEmpty())throw new RuntimeException("单据:"+id+" 无需质检");
    }

    public String generateQualityDetail(JSONArray ids,JSONObject param){
        List<String> stringList = ids.stream().map(o -> (String) o).collect(Collectors.toList());
        return generateQualityDetail(stringList,param);
    }

    public void sendQualityTest(QualityDetail qualityDetail){
        OutboxPayloadWrapper wrapper = new OutboxPayloadWrapper();
        wrapper.setSubscriber("servicePlan.server.sendQualityTest");
        wrapper.setAggregateId(qualityDetail.findAggregateId());
        try {
            wrapper.setPayload(JSON.toJSONString(MapUtil.mapper(
                    "MD5",qualityDetail.getQualityOrderId(),
                    "id",qualityDetail.getId(),
                    "planId",qualityDetail.getPlanId()
            )));
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }
        SpringManager.getBean(OutboxEventService.class).sendSaveEvent(wrapper);
    }

    @OutboxSubscriber(id = "servicePlan.server.sendQualityTest",name = "质检明细质检方案")
    public void qualityTest(OutboxPayloadWrapper outboxPayloadWrapper){
        StopWatch sw = new StopWatch();
        sw.start();
        String payload = outboxPayloadWrapper.getPayload();
        JSONObject jo = JSONObject.parseObject(payload);
        QualityDetail qualityDetail = qualityDetailRepository.findOne(jo.getString("id"));
        String s = checkMongoDB(qualityDetail);
        if (s!=null&&!s.equals(qualityDetail.getErrMsg())){
            qualityDetail.setErrMsg(s);
            qualityDetailRepository.saveAndFlush(qualityDetail);
            //重新推送事务发件箱
            sendQualityTest(qualityDetail);
            return;
        }
        sw.stop();
//        sendTimeMongoDB(qualityDetail,sw.getLastTaskTimeMillis(),"1","前置处理");
        sw.start();
        Integer execute = transactionTemplate.execute(status -> {
            try {
                qualityTestProcess(jo);
                return 1;
            } catch (Exception e) {
                status.isRollbackOnly();
                Boolean aBoolean = checkErrMsg(qualityDetail);
                if (!aBoolean)return 1;
                if (!e.getMessage().equals(s))sendMongoDB(qualityDetail,e);
                throw e;
            }
        });
        sw.stop();
//        sendTimeMongoDB(qualityDetail,sw.getLastTaskTimeMillis(),"2","核心处理");
    }
    private String  checkMongoDB(QualityDetail qualityDetail){
        BasicDBObject object = new BasicDBObject();
        object.put("servicePlanId", qualityDetail.getPlanId());
        FindIterable<Document> cursor = mongoTemplate.getCollection(CONST_ERROR_COLLECTION_NAME).find(object);
        JSONArray resultJson = new JSONArray();
        cursor.forEach((Block<? super Document>) value -> resultJson.add(JSONObject.parseObject(value.toJson())));
        if (resultJson.isEmpty()) return null;
        JSONObject maxTime = resultJson.stream().map(o -> (JSONObject)o).max(Comparator.comparing(json -> json.getJSONObject("time").getLong("$numberLong"))).orElse(null);
        return maxTime.getString("msg");
    }
    private Boolean sendMongoDB(QualityDetail qualityDetail,Exception e){
        Document document = new Document();
        document.put("servicePlanId", qualityDetail.getPlanId());
        document.put("data", MapUtil.mapped(qualityDetail));
        document.put("time", new Date().getTime());
        document.put("msg",e.getMessage());
        mongoTemplate.getCollection(CONST_ERROR_COLLECTION_NAME).insertOne(document);
        return true;
    }
    private void sendTimeMongoDB(QualityDetail qualityDetail,Long times,String step,String remark){
        sendTimeMongoDB(qualityDetail,times,new HashMap(),step,remark);
    }
    private void sendTimeMongoDB(QualityDetail qualityDetail,Long times,Map data,String step,String remark){
        Document document = new Document();
        document.put("servicePlanId", qualityDetail.getPlanId());
        document.put("data",data);
        document.put("step",step);
        document.put("time", times);
        document.put("remark",remark);
        mongoTemplate.getCollection(CONST_RUNTIME_COLLECTION_NAME).insertOne(document);
    }

    private Boolean checkErrMsg(QualityDetail qualityDetail){
        String errMsg = qualityDetail.getErrMsg();
//        if (errMsg!=null&&errMsg.equals(e.getMessage()))return true;
//        ServicePlan servicePlan = servicePlanRepository.findOne(qualityDetail.getPlanId());
        if ("单据已终止".equals(errMsg))return false;
        if ("单据已质检".equals(errMsg)){
            qualityDetail.setFinish(true);
            qualityDetailRepository.saveAndFlush(qualityDetail);
            return false;
        }
//        if (!errMsg.equals(e.getMessage()))return false;
//        sendQualityTest(qualityDetail);
        return true;
    }

    private void qualityTestProcess(JSONObject jo) {
        String planId = jo.getString("planId");
        QualityDetail qualityDetail = qualityDetailRepository.findOne(jo.getString("id"));
        String operator = qualityDetail.getOperatorId();
        PartyRole partyRole = SpringManager.getBean(PartyRoleRepository.class).findOne(operator);
        ServicePlan servicePlan = servicePlanRepository.findOne(planId);

        if (servicePlan.getQualityTestDate()!=null)throw new RuntimeException("单据已质检");
        if (servicePlan.getTerminationDate()!=null)throw new RuntimeException("单据已终止");
//        ServicePlanItem noNeedQualityTest = servicePlan.checkNoNeedQualityTest();
//        if (noNeedQualityTest !=null)throw new RuntimeException("商品["+noNeedQualityTest.getProduct().getName()+"]不可质检");
        List<ServicePlanItem> servicePlanItems = servicePlan.returnNeedQualityTestItems();
        if (servicePlanItems.isEmpty())throw new RuntimeException("无需质检");
//        sendTimeMongoDB(qualityDetail,sw.getLastTaskTimeMillis(),mapper("size",servicePlanItems.size()),"2.1","数据准备");
        servicePlanItems
                .forEach(servicePlanItem -> {
                    servicePlanItem.setQualityTestDate(new Date());
                    servicePlanItem.setQualityTestUid(partyRole);
                    servicePlanItem.setQualityFlag(qualityDetail.isQualityFlag());
                    servicePlanItem.setQualityTestPic(qualityDetail.getQualityTestPic());
                    servicePlanItem.setSelected(true);
                    servicePlanItem.setWorkState("quality");
                });
//        sendTimeMongoDB(qualityDetail,sw.getLastTaskTimeMillis(),"2.2","修改质检状态");
        servicePlanceService.qualityTest(servicePlan);
//        sendTimeMongoDB(qualityDetail,sw.getLastTaskTimeMillis(),"2.3","创建凭证");
        qualityDetail.setFinish(true);
        qualityDetailRepository.saveAndFlush(qualityDetail);
    }

    public void checkCurAmountAndPeriod(ServicePlan servicePlan) {
        LossQuotation lossQuotation = SpringManager.getBean(LossQuotationRepository.class).findOne(servicePlan.getSourceFrom());
        InsuranceRule insuranceRule = SpringManager.getBean(InsuranceRuleService.class)
                .findByDeptAndInsuranceCompany(lossQuotation.getFixedLossDept(), lossQuotation.getInsuranceCompany());
        if (insuranceRule ==null) throw new RuntimeException("没有找到"+ lossQuotation.getInsuranceCompany().getName()+"的门店定损规则");
        PartyRole company = insuranceRule.getCompany();
        InsuranceCompanyDetail companyDetail = SpringManager.getBean(FixedLossToForeign.class)
                .getInsuranceCompanyToClient(insuranceRule.getInsuranceCompany(), lossQuotation.getFixedLossDept());
        PartyRole partner = companyDetail.getClient();
        if (!partner.getParty().isRole("Company")) { /**内部客户不做额度检查**/
            //查询信用额度
            Map<String, Double> ledgerSummary = getCurAmountMap(company, partner);
            //检查账期
            checkPartnerAccountPeriod(company, partner, ledgerSummary.get("period"));
            //判断额度是否足够
            double balance = ledgerSummary.get("credit") + ledgerSummary.get("prepayment_unsettled") - ledgerSummary.get("current_unsettled");
            if (servicePlan.getBalanceAmount() - balance > 0) {
                try {
//                throw new RuntimeException("["+ company.getId() +"]公司与合作伙伴:"+partner.getName()+"["+partner.getId()+"]信用额度不足！请先自行做客户结算！");
                    //应收方`广州市人保财产保险有限公司`挂账额度不足，请先做客户结算！
                    throw new RuntimeException("公司【" + company.getId() + "】应收方'" + partner.getName() + "'挂账额度不足(剩余："+balance+")，请先做客户结算！");
                } catch (Exception e) {
                    throw new RuntimeException(e.getMessage());
                } finally {
                    EventUtil.sendSynEvent(this, "event:dingding.message.publish", MapUtil.mapper(
                            "userList", "055352295820066798,02753,195734655926089443,01232364531421522622,255204694439702617,050055382220899756",
                            "content", "销售金额大于可用余额，请重新确认, 当前可用余额为:"
                                    + Arith.round(balance, 2) + "，公司：" + company.getId() + ",合作伙伴：" + partner.getId()
                    ));
                }
            }
        }
        //判断对平台信用额度， 定损部分不用判断平台额度
//        checkFnToPlatformCurAmount(company,servicePlan.getBalanceAmount());
    }

    /**
     * 定时补偿机制,用于未及时消费的质检信息状态回写
     * @param request
     */
    public void checkOverTimeQualityTestDetail(SimpleRequest request){
        JSONObject jo = request.getJO();
        Integer second = jo.getInteger("second");
        Date date = new Date();
        date.setTime(date.getTime() - (second*1000));

        List<QualityDetail> overTimeDetail = qualityDetailRepository.findAllByFinishAndLastUpdatedStampBeforeAndErrMsgIsNull(false,date);
        if (overTimeDetail.isEmpty())return;

        List<String> aggregateIds = overTimeDetail.stream().map(QualityDetail::findAggregateId).collect(Collectors.toList());
        List<OutboxEvent> outboxEventList = outboxEventRepository.findAll(JpaDsl.toCriteria("aggregateId", "IN", aggregateIds));

        overTimeDetail.forEach(qualityDetail -> {
            OutboxEvent event = outboxEventList.stream().filter(outboxEvent -> outboxEvent.getAggregateId().equals(qualityDetail.findAggregateId())).findFirst().orElse(null);
            qualityDetailEventProcess(qualityDetail, event);
        });

    }

    /**
     * 处理超过规定时间未消费的单据
     * @param qualityDetail
     * @param event
     */
    private void qualityDetailEventProcess(QualityDetail qualityDetail, OutboxEvent event) {
        if (event==null){
            if (qualityDetail.isFinish())return;
            //重新发送
            sendQualityTest(qualityDetail);
//                qualityDetail.setErrMsg("消息丢失,请检查事务发件箱");
            return;
        }
        switch (event.getState()){
            case 0:
                //未消费处理
                this.invoke("outbox.message.batchProcessingByAggregateId", MapUtil.mapper("aggregateId",event.getAggregateId()));
                break;
            case 1://报错
                qualityDetail.setErrMsg(event.getError());
                qualityDetailRepository.saveAndFlush(qualityDetail);
                break;
            case 2://已消费
                break;
                default:
                    break;
        }
    }
    //todo 历史数据跑数脚本

    public List<QualityDetail> synQualityDetail(SimpleRequest request){
        JSONObject jsonObject = request.getJO();
        String id = jsonObject.getString("id");
        int maxNum = jsonObject.getInteger("maxNum");
        Date date = new Date(jsonObject.getLong("timestamp"));
        List<QualityDetail> all = qualityDetailRepository.findByLastUpdatedStampAndId(date,id,PageRequest.of(0, maxNum));
        return all;
    }

    private Map<String, Double> getCurAmountMap(PartyRole company, PartyRole partner) {
        Map<String, Double> ledgerSummary = SpringManager.getBean(LedgerService.class).summary(
                MapUtil.mapper("company",company,  "partner", partner));
        ledgerSummary.putIfAbsent("credit", 0.00);
        ledgerSummary.putIfAbsent("prepayment_unsettled", 0.00);
        ledgerSummary.putIfAbsent("occupy_unsettled", 0.00);
        ledgerSummary.putIfAbsent("current_unsettled", 0.00);
        ledgerSummary.putIfAbsent("discount_unsettled", 0.00);
        ledgerSummary.putIfAbsent("period", 0.00);
        return ledgerSummary;
    }

    /**
     * 检查客户账期
     */
    public void checkPartnerAccountPeriod(PartyRole company,  PartyRole partner, Double period){
        if (period == 0) throw new RuntimeException(String.format("此客户未设置账期，请自行在“客户额度单”设置新账期。客户[%s]公司[%s]",partner.getId(),company.getId()));
        Invoice invoice = SpringManager.getBean(InvoiceRepository.class).findFirstByCompanyIdAndPartnerId(company.getId(), partner.getId());
        if (invoice != null){
            Date now = DateUtil.dateToDate(new Date(),DateUtil.SHOW_DATE_FORMAT);
            Date createDate = DateUtil.dateToDate(invoice.getCreateDate(),DateUtil.SHOW_DATE_FORMAT);
            long betweenDays = (now.getTime() - createDate.getTime()) / (1000L*3600L*24L) - 1;
            if (betweenDays > period) throw new RuntimeException(String.format("客户[%s],公司[%s]存在超过账期的待结算数据，请自行在“客户额度单”设置新账期。",partner.getId(),company.getId()));
        }
    }
    @OutboxSubscriber(name = "APP要求，质检详情变更发信号量给APP")
    public void sendSynQualityTest(QualityDetail qualityDetail){
        Map newMap = new HashMap();
        newMap.put("method","fn.appServer.synQualityDetail");
        newMap.put("timestamp",qualityDetail.getLastUpdatedStamp());
        // 调用App接口 通知接口
        String notifyTopic = "event:pi.app.synAppNotify";
        EventUtil.sendSynEvent(this, notifyTopic, newMap);
    }
    private ServicePlan testQT(SimpleRequest request){
        JSONObject jo = request.getJO();
        QualityDetail qualityDetail = qualityDetailRepository.findOne(jo.getString("id"));
//        String operator = qualityDetail.getOperatorId();
//        PartyRole partyRole = SpringManager.getBean(PartyRoleRepository.class).findOne(operator);
        ServicePlan servicePlan = servicePlanRepository.findOne(qualityDetail.getPlanId());
        //判断账期/信用额度
        boolean isLoss = "LossQuotation".equals(servicePlan.getSourceBusinessType());
        if (isLoss)checkCurAmountAndPeriod(servicePlan);
        return servicePlan;
    }


    private Map<String, Double> checkFnToPlatformCurAmount(PartyRole company,Double amount) {
        List<FnContract> fnContracts = SpringManager.getBean(FnContractRepository.class).qryValidFnContract(company.getId());
        Map<String, Double> curAmount = new HashMap<>();
        if (!fnContracts.isEmpty()){
            FnContract fnContract = fnContracts.get(0);
            Map<String, Double> platform = getCurAmount(fnContract.getCompany(), company);
            if (amount - platform.get("curBalance") > 0){
                throw new RuntimeException("["+ company.getId() +"]公司与平台:"+fnContract.getCompany().getName()+"["+fnContract.getCompany().getId()+"]信用额度不足！请先缴款至平台！");
            }
        }
        return curAmount;
    }

    public Map<String, Double> getCurAmount(PartyRole company,  PartyRole partner) {
        Map<String, Double> returnMap = new HashMap<>();
        Map<String, Double> ledgerSummary = SpringManager.getBean(LedgerService.class).summary(MapUtil.mapper("company",company,"partner", partner));
        ledgerSummary.putIfAbsent("credit", 0.00);
        ledgerSummary.putIfAbsent("prepayment_unsettled", 0.00);
        ledgerSummary.putIfAbsent("current_unsettled", 0.00);
        ledgerSummary.putIfAbsent("period", 0.00);
        returnMap.put("curBalance", ledgerSummary.get("credit") + ledgerSummary.get("prepayment_unsettled") - ledgerSummary.get("current_unsettled"));
        returnMap.put("period", ledgerSummary.get("period"));
        return returnMap;
    }

    public Object checkNeedPriceAudit(SimpleRequest request){
        JSONObject jo = request.getJO();
        String id = jo.getString("id");
        if (id==null)return new JSONObject(MapUtil.mapper("needPriceAudit",false));
        ServicePlan servicePlan = servicePlanRepository.findOne(id);
        if (servicePlan==null)return new JSONObject(MapUtil.mapper("needPriceAudit",false));
        JSONObject jsonObject = servicePlanceService.needPriceAudit(servicePlan, request);
//        return .getBoolean("needPriceAudit");
        List needPriceAuditList = (List) jsonObject.get("needPriceAuditList");
        Map resultMap = new HashMap();
        resultMap.putAll(jsonObject);
        SpringManager.getBean(ServicePlanToInvoke.class).splitPlanItems(resultMap,needPriceAuditList);
        return resultMap;
    }
}
